/*
 * SquareBoard.java
 *
 * This work is free software; you can redistribute it and/or
 * modify it under the terms of the Apache License as published 
 * by the Apache Software Foundation.
 *
 * This work is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * Apache License for more details.
 *
 * Copyright (c) 2011 Tran Hoang Anh (tranhoanganh89@gmail.com). 
 * All rights reserved.
 */
package net.hoanganh.jbraintetris;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Insets;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JComponent;

/**
 * A board is formed by a grid of squares. It can handle and paint the squares
 * states it shelf.
 */
@SuppressWarnings("serial")
public class SquareBoard extends JComponent {
	
	/**
	 * Default message font size.
	 */
	public static final int DEFAULT_FONT_SIZE = 14;

	/**
	 * Default background color.
	 */
	public static final Color DEFAULT_BACKGROUND_COLOR = Color.black;
	
	/**
	 * Default message color.
	 */
	public static final Color DEFAULT_MESSAGE_COLOR = Color.white;

	/**
	 * State of board squares.
	 */
	private Color[][] gridState = null;

	/**
	 * Square size in pixels.
	 */
	private Dimension squareSize = null;

	/**
	 * Board size in squares.
	 */
	private Dimension boardSize = null;

	/**
	 * Board size in pixels.
	 */
	private Dimension size = null;

	/**
	 * Boundary around painting area.
	 */
	private Insets inset = new Insets(0, 0, 0, 0);

	/**
	 * Background color.
	 */
	private Color backgroundColor = DEFAULT_BACKGROUND_COLOR;

	/**
	 * Message color.
	 */
	private Color messageColor = DEFAULT_MESSAGE_COLOR;
	
	/**
	 * Message which is display in central of the board. If null, no message is
	 * displayed.
	 */
	private String message;
	
	/**
	 * Font size.
	 */
	private int fontSize = DEFAULT_FONT_SIZE;
	
	/**
	 * Cache for brighter colors.
	 */
	private Map<Color, Color> brighterColors = null;

	/**
	 * Cache for darker colors.
	 */
	private Map<Color, Color> darkerColors = null;

	/**
	 * Create new square board.
	 * 
	 * @param width
	 *            width in squares
	 * 
	 * @param height
	 *            height in squares
	 */
	public SquareBoard(int width, int height) {
		if (width <= 0 || height <= 0) {
			throw new IllegalArgumentException("board size is not valid.");
		}

		boardSize = new Dimension(width, height);
		gridState = new Color[width][height];
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				gridState[x][y] = null;
			}
		}
	}

	/**
	 * Get displaying message.
	 * 
	 * @return the message
	 */
	public String getMessage() {
		return message;
	}

	/**
	 * Set displaying message. If null, no message is displayed.
	 * 
	 * @param message
	 *            the message to set
	 */
	public void setMessage(String message) {
		this.message = message;
		redraw();
	}

	/**
	 * Get square size.
	 * 
	 * @return the squareSize
	 */
	public Dimension getSquareSize() {
		return squareSize;
	}

	/**
	 * Get board size in squares.
	 * 
	 * @return the boardSize
	 */
	public Dimension getBoardSize() {
		return boardSize;
	}

	/**
	 * Get board insets.
	 * 
	 * @return the insets
	 */
	public Insets getInset() {
		return inset;
	}

	/**
	 * Set background color. If null, use default background color.
	 * 
	 * @param color
	 *            to set background
	 */
	public void setBackgroundColor(Color color) {
		if (color != null) {
			this.backgroundColor = color;
		} else {
			this.backgroundColor = DEFAULT_BACKGROUND_COLOR;
		}
	}

	/**
	 * Get background color.
	 * 
	 * @return the backgroundColor
	 */
	public Color getBackgroundColor() {
		return backgroundColor;
	}

	/**
	 * Check if square is empty.
	 * 
	 * @return true if empty, otherwise false
	 */
	public boolean isEmpty(int x, int y) {
		if (x < 0 || x >= boardSize.width || y < 0 || y >= boardSize.height) {
			return true;
		}
		return gridState[x][y] == null;
	}

	/**
	 * Set color for board square.
	 * 
	 * @param x
	 *            x position in square
	 * 
	 * @param y
	 *            y position in square
	 * 
	 * @param color
	 *            color of square
	 */
	public void setSquareColor(int x, int y, Color color) {
		if (x < 0 || x >= boardSize.width || y < 0 || y >= boardSize.height) {
			return;
		}
		gridState[x][y] = color;
	}

	/**
	 * Return color of specific square.
	 * 
	 * @param x
	 *            x position in square
	 * 
	 * @param y
	 *            y position in square
	 * 
	 * @return color of square
	 */
	public Color getSquareColor(int x, int y) {
		if (x < 0 || x >= boardSize.width || y < 0 || y >= boardSize.height) {
			return null;
		}
		return gridState[x][y];
	}

	/**
	 * Clear all content of board.
	 */
	public void clear() {
		for (int x = 0; x < boardSize.width; x++) {
			for (int y = 0; y < boardSize.height; y++) {
				setSquareColor(x, y, null);
			}
		}
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);

		if (size == null || !size.equals(getSize())) {
			size = getSize();
			squareSize = new Dimension();
			squareSize.width = size.width / boardSize.width;
			squareSize.height = size.height / boardSize.height;
			inset.top = 0;
			inset.left = (size.width - boardSize.width * squareSize.width) / 2;
			inset.right = inset.left;
			inset.bottom = (size.height - boardSize.height * squareSize.height);
		}

		paintBackground(g);
		paintContent(g);
		paintAuxiliary(g);
		paintMessage(g);
	}

	/**
	 * Paint background.
	 * 
	 * @param g
	 *            graphics context
	 */
	protected void paintBackground(Graphics g) {
		g.setColor(backgroundColor);
		g.fillRect(0, 0, size.width, size.height);
	}

	/**
	 * Paint board content.
	 * 
	 * @param g
	 *            graphics context
	 */
	protected void paintContent(Graphics g) {
		for (int y = 0; y < boardSize.height; y++) {
			for (int x = 0; x < boardSize.width; x++) {
				if (!isEmpty(x, y)) {
					paintSquare(g, x, y, gridState[x][y]);
				}
			}
		}
	}

	/**
	 * Paint a board square.
	 * 
	 * @param g
	 *            graphics context
	 * 
	 * @param x
	 *            x position in pixels
	 * 
	 * @param y
	 *            y position in pixels
	 * 
	 * @param color
	 *            square color
	 */
	protected void paintSquare(Graphics g, int x, int y, Color color) {
		int xMin = getInset().left + x * getSquareSize().width + 1;
		int yMin = getInset().top + y * getSquareSize().height + 1 ;
		int xMax = xMin + getSquareSize().width - 1;
		int yMax = yMin + getSquareSize().height - 1;

		// Draw main square
		g.setColor(color);
		g.fillRect(xMin, yMin, getSquareSize().width, getSquareSize().height);

		// Draw brighter line in top left corner.
		g.setColor(getBrighterColor(color));
		for (int i = 0; i < getSquareSize().width / 10; i++) {
			g.drawLine(xMin + i, yMin + i, xMax - i, yMin + i);
			g.drawLine(xMin + i, yMin + i, xMin + i, yMax - i);
		}

		// Draw darker line in bottom right corner.
		g.setColor(getDarkerColor(color));
		for (int i = 0; i < getSquareSize().width / 10; i++) {
			g.drawLine(xMin + i, yMax - i, xMax - i, yMax - i);
			g.drawLine(xMax - i, yMin + i, xMax - i, yMax - i);
		}
	}
	
	/**
	 * Paint auxiliary such as border.
	 * 
	 * @param g
	 *            graphics context
	 */
	protected void paintAuxiliary(Graphics g) {
	}

	/**
	 * Paint message in central of board.
	 * 
	 * @param g
	 *            graphics context
	 */
	private void paintMessage(Graphics g) {
		if (message != null && message.trim().length() != 0) {
			g.setColor(messageColor);
			g.setFont(new Font("SansSerif", Font.BOLD, fontSize));
			int messageWidth = g.getFontMetrics().stringWidth(message);
			g.drawString(message, inset.left
					+ (squareSize.width * boardSize.width - messageWidth) / 2,
					inset.top + (squareSize.height * boardSize.height) / 2);
		}
	}

	public void redraw() {
		validate();
		repaint();
	}

	/**
	 * Get brighter version of given color.
	 * 
	 * @param color
	 *            given color
	 * 
	 * @return a brighter color
	 */
	private Color getBrighterColor(Color color) {
		if (brighterColors == null) {
			brighterColors = new HashMap<Color, Color>();
		}
		if (!brighterColors.containsKey(color)) {
			brighterColors.put(color, color.brighter().brighter());
		}
		return brighterColors.get(color);
	}

	/**
	 * Get darker version of given color.
	 * 
	 * @param color
	 *            given color
	 * 
	 * @return a darker color
	 */
	private Color getDarkerColor(Color color) {
		if (darkerColors == null) {
			darkerColors = new HashMap<Color, Color>();
		}
		if (!darkerColors.containsKey(color)) {
			darkerColors.put(color, color.darker().darker());
		}
		return darkerColors.get(color);
	}
}